package br.ufrn.dca.identificacao;

import br.ufrn.dca.interfaceGrafica.Fluxo;
import java.text.DecimalFormat;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;

/**
 * Classe que responsavel por implementar as funcionalidade do metodo do rele
 *
 * @author allan
 */
public class Rele {

    /**
     * Saída do relé no estado ligado
     */
    private double umax;
    /**
     * Saída do relé no estado desligado
     */
    private double umin;
    /**
     * Valor de histerese
     */
    private double h;
    /**
     * Referência na qual o relé vai oscilar
     */
    private double ref;
    /**
     * Amplitude da oscilação gerada
     */
    private double amp;
    /**
     * Periodo da oscilação gerada
     */
    private double pu;
    /**
     * Tempo que o rele fica em baixo
     */
    private double tAlto;
    /**
     * Tempo que o rele fica em alto
     */
    private double tBaixo;
    /**
     * variavel de tempo auxiliar
     */
    private double ti;
    /**
     * variavel de tempo auxiliar
     */
    private double tf;
    /**
     * indica se o relé está ligado ou desligado
     */
    private boolean ligado;
    /**
     * Indica se a saída do relé já foi ajustada
     */
    private boolean ajustado;
    /**
     *
     */
    private double saida;
    /**
     * Lista contendo os valores da saída da planta
     */
    private Complex y[];
    /**
     * Contador para garantir 2^n elementos em listaY
     */
    private int cont;
    /**
     * Varaivel para auxiliar a parada do relé
     */
    private Fluxo fluxo;
    /**
     * Representa o sistema que será identificado
     */
    private Nopdt nopdt;
    /**
     * Ganho estatico da planta
     */
    private double k;
    /**
     * tamanho do vetor de amostras de y
     */
    private int n;

    /**
     *
     * @param umin
     * @param umax
     * @param ref
     */
    public Rele(double umin, double umax, double ref) {
        this.umax = umax;
        this.umin = umin;
        this.ref = ref;
    }

    public Rele(Fluxo fluxo) {
        this.fluxo = fluxo;
        this.ref = 10;
        this.saida = 2;
        this.h = .2;
        ligado = true;
        ajustado = false;
        n = 128;
        this.y = new Complex[n];
        cont = 0;
        this.nopdt = new Nopdt();
        
        this.k = 7.44;
        this.umax = 2.7;
        this.umin = -0.02;
        
////        
//        this.k = 23.11;        
//        this.umax = 1.1;
//        this.umin= -.2;
    }

    public void reset() {
        ajustado = false;
        cont = 0;
        n = 128;
        this.amp = 0;
        this.pu = 0;
        this.y = new Complex[n];
        this.nopdt.setDelay(0);
        this.nopdt.setK(0);
        this.nopdt.setTao(0);
        this.fluxo.setSitemaIdentificado(false);        
    }

    /**
     * Método responsavel pela oscilação da planta
     *
     * @param nivel
     * @param tempo
     *
     * @return
     */
    public double oscilar(double nivel, double tempo) {

        //nivel passou da referencia - desliga relé
        if (nivel > (ref + h) & ligado) {
            ligado = false;
            ti = tempo;
            tAlto = tempo;
            tAlto = tAlto - tf;
            saida = umin;
        }

        //nivel passou da referencia - liga relé
        if (nivel < (ref - h) & !ligado) {
            ligado = true;
            tf = tempo;
            tBaixo = tempo;
            tBaixo = tBaixo - ti;

            if (!ajustado) {
                ajustarSaida();
            }
            saida = umax;
        }

        if (ajustado & cont < n) {
            y[cont] = new Complex(nivel);
            cont++;
        }

        if (cont == n & !fluxo.isSitemaIdentificado()) {
            calculaParamentros();
            double a = (Math.abs(umax) + Math.abs(umin))/2;
            nopdt.identificacao(a, h, pu, amp, k);
            cont++;
            fluxo.setExecutandoRele(false);
            fluxo.setSitemaIdentificado(true);
        }

        return saida;
    }

    /**
     * Ajusta a saída do relé de acordo
     */
    public void ajustarSaida() {
        DecimalFormat f = new DecimalFormat("0.00");
        double c = Math.abs(tAlto - tBaixo);        
        c = Double.parseDouble(f.format(c).replace(",", "."));
        System.out.println(c);
        if (c  > .12 & !ajustado) {
            
            if (tBaixo > tAlto) {
                umin -= 0.05;
            }

            if (tAlto > tBaixo) {
                umin += 0.05;
            }                      
        } else{
            ajustado = true;
        }
    }

    /**
     * Calcula o periodo e a amplitude da oscilação
     */
    private void calculaParamentros() {
        pu = tAlto + tBaixo;
        amp = calculaAmp();
    }

    /**
     * Calcula a amplitude da oscilação usando FFT
     *
     * @return
     */
    private double calculaAmp() {
        double resultado = 0;
        FastFourierTransformer transf = new FastFourierTransformer(DftNormalization.STANDARD);
        Complex fft[] = transf.transform(y, TransformType.FORWARD);
        int i = 0;
        for (Complex complex : fft) {
//            System.out.println(complex.getReal());
            double r = fix(complex.getReal());
            double j = fix(complex.getImaginary());
            fft[i] = new Complex(r, j);
            i++;
        }
//        System.out.println("\n");
        Complex ifft[] = transf.transform(fft, TransformType.INVERSE);

        for (Complex complex : ifft) {
            resultado = Math.max(complex.getReal(), resultado);            
//            System.out.println(complex.getReal());
        }
        return (resultado - ref);
    }
    
    /**
     * Função fix do Matlab
     * @param valor
     * @return 
     */
    public double fix(double valor) {
        double resultado = 0;
        double aux = valor/10;
        if (valor > 0) {
            resultado = Math.floor(aux);
        } else {
            resultado = Math.ceil(aux);
        }
        return (resultado*10);
    }

    public double getUmax() {
        return umax;
    }

    public void setUmax(double umax) {
        this.umax = umax;
    }

    public double getUmin() {
        return umin;
    }

    public void setUmin(double umin) {
        this.umin = umin;
    }

    public double getH() {
        return h;
    }

    public void setH(double h) {
        this.h = h;
    }

    public double getRef() {
        return ref;
    }

    public void setRef(double ref) {
        this.ref = ref;
    }

    public double getAmp() {
        return amp;
    }

    public void setAmp(double amp) {
        this.amp = amp;
    }

    public double getPu() {
        return pu;
    }

    public void setPu(double pu) {
        this.pu = pu;
    }

    public double gettAlto() {
        return tAlto;
    }

    public void settAlto(double tAlto) {
        this.tAlto = tAlto;
    }

    public double gettBaixo() {
        return tBaixo;
    }

    public void settBaixo(double tBaixo) {
        this.tBaixo = tBaixo;
    }

    public double getTi() {
        return ti;
    }

    public void setTi(double ti) {
        this.ti = ti;
    }

    public double getTf() {
        return tf;
    }

    public void setTf(double tf) {
        this.tf = tf;
    }

    public boolean isLigado() {
        return ligado;
    }

    public void setLigado(boolean ligado) {
        this.ligado = ligado;
    }

    public boolean isAjustado() {
        return ajustado;
    }

    public void setAjustado(boolean ajustado) {
        this.ajustado = ajustado;
    }

    public double getSaida() {
        return saida;
    }

    public void setSaida(double saida) {
        this.saida = saida;
    }

    public Complex[] getY() {
        return y;
    }

    public void setY(Complex[] y) {
        this.y = y;
    }

    public int getCont() {
        return cont;
    }

    public void setCont(int cont) {
        this.cont = cont;
    }

    public Fluxo getFluxo() {
        return fluxo;
    }

    public void setFluxo(Fluxo fluxo) {
        this.fluxo = fluxo;
    }

    public Nopdt getNopdt() {
        return nopdt;
    }

    public void setNopdt(Nopdt nopdt) {
        this.nopdt = nopdt;
    }
}
